﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Scripting;
using Microsoft.Scripting.Ast;
using Microsoft.Linq.Expressions;

namespace DlrBasic.Parser.Ast
{
    abstract class ProgramBase : DlrBasicNode
    {
        public ProgramBase(SourceSpan span)
            : base(span)
        {
        }

        internal abstract void Visit(StatementsVisitor visitor);


        internal virtual Expression Generate(DlrBasicGenerator g)
        {
            Expression initialize0 = GetInitializationExpression(g);

            Expression statementsVariable, nextStatementsVariable;
            Expression initialize1 = CreateStatementsArrays(g,
                out statementsVariable, out nextStatementsVariable);

            Expression endTest = ConditionalExpression.GreaterThanOrEqual(
                g.GetCurrentStatementVariable(), Utils.Constant(0));

            Expression executeStatement = Expression.Invoke(
                Expression.ArrayAccess(statementsVariable, g.GetCurrentStatementVariable()),
                g.StateVariable);
            LambdaExpression executeStatementLamdba = Expression.Lambda(executeStatement);
            Expression executeStatementDelegate = g.GetTempVariable("executeStatementDelegate", executeStatementLamdba.Type);
            Expression initialize2 = Expression.Assign(executeStatementDelegate, executeStatementLamdba);

            Expression ignoreStatement = Expression.Assign(
                g.GetCurrentStatementVariable(), 
                Expression.ArrayAccess(nextStatementsVariable, g.GetCurrentStatementVariable()));

            Expression callbackResult = g.GetTempVariable("callbackResult", typeof(StatementExecutionAction));
            Expression callbackDelegateCall = Expression.Invoke(g.StatementCallbackDelegate,
                Utils.CodeContext(), g.StateVariable);
            Expression callbackInvoke = Expression.Assign(callbackResult, callbackDelegateCall);

            LabelTarget endSwith = Expression.Label();
            SwitchBuilder switchBuilder = Utils.Switch(Expression.ConvertHelper(callbackResult, typeof(int)), endSwith);
            switchBuilder.Case((int)StatementExecutionAction.Execute,
                Expression.Block(executeStatement, Expression.Break(endSwith)));
            switchBuilder.Case((int)StatementExecutionAction.Ignore,
                Expression.Block(ignoreStatement, Expression.Break(endSwith)));

            Expression programLoop = Utils.While(endTest,
                Expression.Block(callbackInvoke, switchBuilder.ToExpression()),
                Expression.Empty());

            return Utils.Block(
                SourceSpan, initialize0, initialize1, initialize2, programLoop);
        }

        private Expression CreateStatementsArrays(DlrBasicGenerator generator, out Expression statementsVariable, out Expression nextStatementsVariable)
        {
            StatementGenerator statements = new StatementGenerator(generator);
            Visit(statements);

            Expression statementsArray = Expression.NewArrayInit(typeof(StatementInvoke),
                statements.GetStatementInvokes());
            statementsVariable = generator.GetTempVariable("statements", statementsArray.Type);
            Expression initializeStatements = Expression.Assign(statementsVariable, statementsArray);

            nextStatementsVariable = generator.GetTempVariable("nextStatements", typeof(int[]));
            Expression initializeNextStatements = Expression.Assign(nextStatementsVariable, 
                Expression.Constant(generator.GetNextStatementNumbersMap()));

            return Expression.Block(initializeStatements, initializeNextStatements);
        }

        private Expression GetInitializationExpression(DlrBasicGenerator generator)
        {
            Expression memory = generator.MemoryVariable;
            Expression forStack = Expression.New(typeof(Runtime.DlrBasicForStack).GetConstructors()[0]);
            Expression gosubStack = Expression.New(typeof(Runtime.DlrBasicGosubStack).GetConstructors()[0]);
            Expression userFunctions = generator.UserFunctionsVariable;
            Expression debugInfo = CreateDebugInfo(generator);
            Expression startStatement = Expression.Constant(0);
            Expression statementCallback = generator.StatementCallbackDelegate;

            Expression state = Expression.New(typeof(Runtime.DlrBasicState).GetConstructors()[0], 
                    statementCallback, memory, forStack, gosubStack,
                    userFunctions, debugInfo, startStatement);

            Expression initialization = Expression.Assign(generator.StateVariable, state);

            return Expression.Block(initialization);
        }

        protected abstract Expression CreateDebugInfo(DlrBasicGenerator g);

        private class StatementGenerator : StatementsVisitor
        {
            private DlrBasicGenerator generator;
            private List<Expression> expressions;

            public StatementGenerator(DlrBasicGenerator generator)
            {
                this.generator = generator;

                expressions = new List<Expression>();
            }

            public override void BasicProgram(BasicProgram program)
            {
            }

            public override void Line(Line line)
            {
            }

            public override void InteractiveLine(InteractiveLine line)
            {
            }

            public override void Statement(Statement statement)
            {
                int state = generator.GetStatementNumber(statement);
                LambdaExpression lambda = statement.GenerateExecutableStatement(generator, state.ToString());

                if (expressions.Count != state)
                {
                    while (expressions.Count <= state)
                        expressions.Add(null);
                    expressions[state] = lambda;
                }
                else
                    expressions.Add(lambda);
            }

            public override void IfThen(If @if)
            {
            }

            public override void IfElse(If @if)
            {
            }

            public override void IfEnd(If @if)
            {
            }

            internal Expression[] GetStatementInvokes()
            {
                return expressions.ToArray();
            }
        }
    }
}

